﻿using System;
using GeoAPI.Geometries;
using NetTopologySuite.Geometries;

namespace NetTopologySuite.Algorithm
{
	public class MinimumDiameter
	{
		public static IGeometry GetMinimumRectangle(IGeometry geom)
		{
			return new MinimumDiameter(geom).GetMinimumRectangle();
		}

		public static IGeometry GetMinimumDiameter(Geometry geom)
		{
			return new MinimumDiameter(geom).Diameter;
		}

		public MinimumDiameter(IGeometry inputGeom) : this(inputGeom, false)
		{
		}

		public MinimumDiameter(IGeometry inputGeom, bool isConvex)
		{
			this._inputGeom = inputGeom;
			this._isConvex = isConvex;
		}

		public double Length
		{
			get
			{
				this.ComputeMinimumDiameter();
				return this._minWidth;
			}
		}

		public Coordinate WidthCoordinate
		{
			get
			{
				this.ComputeMinimumDiameter();
				return this._minWidthPt;
			}
		}

		public ILineString SupportingSegment
		{
			get
			{
				this.ComputeMinimumDiameter();
				return this._inputGeom.Factory.CreateLineString(new Coordinate[]
				{
					this._minBaseSeg.P0,
					this._minBaseSeg.P1
				});
			}
		}

		public ILineString Diameter
		{
			get
			{
				this.ComputeMinimumDiameter();
				if (this._minWidthPt == null)
				{
                    return this._inputGeom.Factory.CreateLineString((ICoordinateSequence)null);
				}
				Coordinate coordinate = this._minBaseSeg.Project(this._minWidthPt);
				return this._inputGeom.Factory.CreateLineString(new Coordinate[]
				{
					coordinate,
					this._minWidthPt
				});
			}
		}

		private void ComputeMinimumDiameter()
		{
			if (this._minWidthPt != null)
			{
				return;
			}
			if (this._isConvex)
			{
				this.ComputeWidthConvex(this._inputGeom);
				return;
			}
			IGeometry convexHull = new ConvexHull(this._inputGeom).GetConvexHull();
			this.ComputeWidthConvex(convexHull);
		}

		private void ComputeWidthConvex(IGeometry convexGeom)
		{
			if (convexGeom is IPolygon)
			{
				this._convexHullPts = ((IPolygon)convexGeom).ExteriorRing.Coordinates;
			}
			else
			{
				this._convexHullPts = convexGeom.Coordinates;
			}
			if (this._convexHullPts.Length == 0)
			{
				this._minWidth = 0.0;
				this._minWidthPt = null;
				this._minBaseSeg = null;
				return;
			}
			if (this._convexHullPts.Length == 1)
			{
				this._minWidth = 0.0;
				this._minWidthPt = this._convexHullPts[0];
				this._minBaseSeg.P0 = this._convexHullPts[0];
				this._minBaseSeg.P1 = this._convexHullPts[0];
				return;
			}
			if (this._convexHullPts.Length == 2 || this._convexHullPts.Length == 3)
			{
				this._minWidth = 0.0;
				this._minWidthPt = this._convexHullPts[0];
				this._minBaseSeg.P0 = this._convexHullPts[0];
				this._minBaseSeg.P1 = this._convexHullPts[1];
				return;
			}
			this.ComputeConvexRingMinDiameter(this._convexHullPts);
		}

		private void ComputeConvexRingMinDiameter(Coordinate[] pts)
		{
			this._minWidth = double.MaxValue;
			int startIndex = 1;
			LineSegment lineSegment = new LineSegment();
			for (int i = 0; i < pts.Length - 1; i++)
			{
				lineSegment.P0 = pts[i];
				lineSegment.P1 = pts[i + 1];
				startIndex = this.FindMaxPerpDistance(pts, lineSegment, startIndex);
			}
		}

		private int FindMaxPerpDistance(Coordinate[] pts, LineSegment seg, int startIndex)
		{
			double num = seg.DistancePerpendicular(pts[startIndex]);
			double num2 = num;
			int num3 = startIndex;
			int num4 = num3;
			while (num2 >= num)
			{
				num = num2;
				num3 = num4;
				num4 = MinimumDiameter.NextIndex(pts, num3);
				num2 = seg.DistancePerpendicular(pts[num4]);
			}
			if (num < this._minWidth)
			{
				this._minPtIndex = num3;
				this._minWidth = num;
				this._minWidthPt = pts[this._minPtIndex];
				this._minBaseSeg = new LineSegment(seg);
			}
			return num3;
		}

		private static int NextIndex(Coordinate[] pts, int index)
		{
			index++;
			if (index >= pts.Length)
			{
				index = 0;
			}
			return index;
		}

		public IGeometry GetMinimumRectangle()
		{
			this.ComputeMinimumDiameter();
			if (this._minWidth != 0.0)
			{
				double num = this._minBaseSeg.P1.X - this._minBaseSeg.P0.X;
				double num2 = this._minBaseSeg.P1.Y - this._minBaseSeg.P0.Y;
				double num3 = double.MaxValue;
				double num4 = double.MinValue;
				double num5 = double.MaxValue;
				double num6 = double.MinValue;
				for (int i = 0; i < this._convexHullPts.Length; i++)
				{
					double num7 = MinimumDiameter.ComputeC(num, num2, this._convexHullPts[i]);
					if (num7 > num4)
					{
						num4 = num7;
					}
					if (num7 < num3)
					{
						num3 = num7;
					}
					double num8 = MinimumDiameter.ComputeC(-num2, num, this._convexHullPts[i]);
					if (num8 > num6)
					{
						num6 = num8;
					}
					if (num8 < num5)
					{
						num5 = num8;
					}
				}
				LineSegment line = MinimumDiameter.ComputeSegmentForLine(-num, -num2, num6);
				LineSegment line2 = MinimumDiameter.ComputeSegmentForLine(-num, -num2, num5);
				LineSegment lineSegment = MinimumDiameter.ComputeSegmentForLine(-num2, num, num4);
				LineSegment lineSegment2 = MinimumDiameter.ComputeSegmentForLine(-num2, num, num3);
				Coordinate coordinate = lineSegment.LineIntersection(line);
				Coordinate coordinate2 = lineSegment2.LineIntersection(line);
				Coordinate coordinate3 = lineSegment2.LineIntersection(line2);
				Coordinate coordinate4 = lineSegment.LineIntersection(line2);
				ILinearRing shell = this._inputGeom.Factory.CreateLinearRing(new Coordinate[]
				{
					coordinate,
					coordinate2,
					coordinate3,
					coordinate4,
					coordinate
				});
				return this._inputGeom.Factory.CreatePolygon(shell, null);
			}
			if (this._minBaseSeg.P0.Equals2D(this._minBaseSeg.P1))
			{
				return this._inputGeom.Factory.CreatePoint(this._minBaseSeg.P0);
			}
			return this._minBaseSeg.ToGeometry(this._inputGeom.Factory);
		}

		private static double ComputeC(double a, double b, Coordinate p)
		{
			return a * p.Y - b * p.X;
		}

		private static LineSegment ComputeSegmentForLine(double a, double b, double c)
		{
			Coordinate p;
			Coordinate p2;
			if (System.Math.Abs(b) > System.Math.Abs(a))
			{
				p = new Coordinate(0.0, c / b);
				p2 = new Coordinate(1.0, c / b - a / b);
			}
			else
			{
				p = new Coordinate(c / a, 0.0);
				p2 = new Coordinate(c / a - b / a, 1.0);
			}
			return new LineSegment(p, p2);
		}

		private readonly IGeometry _inputGeom;

		private readonly bool _isConvex;

		private Coordinate[] _convexHullPts;

		private LineSegment _minBaseSeg = new LineSegment();

		private Coordinate _minWidthPt;

		private int _minPtIndex;

		private double _minWidth;
	}
}
